package roborally;

import be.kuleuven.cs.som.annotate.*;
/**
 * A class that defines robots.
 * 
 * @invar 	XPos must be a valid coordinate.
 * 			|isValidCoord(this.getXPos())
 * @invar	YPos must be a valid coordinate.
 * 			|isValidCoord(this.getYPos())
 * @invar 	The robot must have a valid energy.
 * 			|isValidEnergy(this.getEnergy())
 * @author 	Joren Van den Brande and Michael Vandendriessche
 * @version 1.0
 */
public class Robot implements IRobot{
	
	///////////////////////////////////////////////////
	//VARIABLES
	///////////////////////////////////////////////////
	/**
	 * A variable that contains the X coordinate of this robot.
	 */
	private long XPos;
	
	/**
	 * A variable that contains the Y coordinate of this robot.
	 */
	private long YPos;
	
	/**
	 * A variable that contains the direction of this robot.
	 */
	//private Direction direction;
	private int direction;
	
	/**
	 * A variable that contains the energy of this robot.
	 */
	private double energy;
	
	///////////////////////////////////////////////////
	//CONSTANTS
	///////////////////////////////////////////////////
	/**
	 * A variable that contains the size of the board.
	 */
	private static final long BOARDSIZE = Long.MAX_VALUE;
	
	/**
	 * A variable that contains the maximum energy a robot can have.
	 */
	private final double MAXENERGY = 20000;
	
	/**
	 * A constant that defines the cost to turn the robot.
	 */
	private final double COST_TURN = 100;
	
	/**
	 * A constant that defines the cost to move the robot.
	 */
	private final double COST_MOVE = 500;
	
	///////////////////////////////////////////////////
	//PUBLIC METHODS
	///////////////////////////////////////////////////
	
	/**
	 * Initialize a new robot with given x and y coordinates and given direction and energy.
	 * 
	 * @param 	x
	 * 			The x coordinate of the robot.
	 * @param 	y
	 * 			The y coordinate of the robot.
	 * @param 	dir
	 * 			The direction the robot is facing.
	 * @param 	energy
	 * 			The amount of energy the robot currently has.
	 * @throws 	IllegalArgumentException
	 * 			The x or y coordinate is not valid.
	 * 			| !isValidCoord(x) || !isValidCoord(y)
	 * @pre 	The energy must be a valid amount.
	 * 			| isValidEnergy(energy)
	 * @post 	The new x coordinate equals the given x coordinate.
	 * 			| new.getXPos() == x
	 * @post 	The new y coordinate equals the given y coordinate.
	 * 			| new.getYPos() == y
	 * @post	The new direction equals the given direction.
	 * 			| new.getDirection() == dir
	 * @post 	The new energy equals the given energy.
	 * 			| new.getEnergy() == energy
	 */
	public Robot(long x, long y, int dir, double energy) throws IllegalArgumentException{		
		try{					//defensive
			setXPos(x);
			setYPos(y);
		}
		catch(IllegalArgumentException e){
			throw e;
		}
			
		setDirection(dir);		//total
		// setEnergy(energy) will check the precondition.
		setEnergy(energy);		//nominal
	}
	
	
	
	/**
	 * Return the size of the board.
	 */
	@Basic @Immutable
	public long getBoardSize(){
		return BOARDSIZE;
	}
	
	/**
	 * Return the energy required to turn.
	 */
	@Basic @Immutable
	public double getCostTurn(){
		return this.COST_TURN;
	}
	
	/**
	 * Return the energy required to move.
	 */
	@Basic @Immutable
	public double getCostMove(){
		return this.COST_MOVE;
	}
	
	/**
	 * Return the X coordinate of this robot.
	 */
	@Basic
	public long getXPos(){
		return XPos;
	}
	
	/**
	 * Return the Y coordinate of this robot.
	 */
	@Basic
	public long getYPos(){
		return YPos;
	}
	
	/**
	 * Returns the direction the robot is currently facing.
	 */
	@Basic
	public int getDirection(){
		return this.direction;
	}
	
	/**
	 * Returns the energy the robot currently has.
	 */
	@Basic
	public double getEnergy(){
		return this.energy;
	}
	
	/**
	 * Returns the maximum amount of energy a robot can have.
	 */
	@Basic @Immutable
	public double getMaxEnergy(){
		return MAXENERGY;
	}
	
	/**
	 * A method to adjust the X coordinate.
	 * @param 	x
	 * 			The new x coordinate of the robot.
	 * @throws 	IllegalArgumentException
	 * 			x has to be a valid coordinate.
	 * 			| ! isValidCoord(x)
	 * @post 	The new x coordinate of the robot equals the parameter x.
	 * 			| new.XPos == x
	 */
	 public void setXPos(long x)throws IllegalArgumentException{
		 if(! isValidCoord(x))
			 throw new IllegalArgumentException();
		 this.XPos = x;
	 }
	 
	/**
	 * A method to adjust the Y coordinate.
	 * @param 	y
	 * 			The new y coordinate of the robot.
	 * @throws 	IllegalArgumentException
	 * 			y has to be a valid coordinate.
	 * 			| ! isValidCoord(y)
	 * @post 	The new y coordinate of the robot equals the parameter y.
	 * 			| new.YPos == y
	 */
	 public void setYPos(long y)throws IllegalArgumentException{
		 if(! isValidCoord(y))
			 throw new IllegalArgumentException();
		 this.YPos = y;
	 }
	 
	 /**
	  * Set the direction that the robot is facing to the given direction.
	  * 
	  * @param 	dir
	  * 		The new direction that the robot will face. 0 is up, 1 is right, 2 is down, 3 is left.
	  * @post 	If the given direction is a valid direction, let the robot face this direction.
	  * 		| if(isValidDirection(dir)) then new.getDirection() == dir
	  * @post	If the given direction is not a valid direction , don't change the robots direction.
	  * 		| if(!isValidDirection(dir)) then new.getDirection() == getDirection()
	  */
	  //@post	If the given direction is not a valid direction (null), don't change the robots direction.
	  //		| if(dir == null) then new.getDirection() == getDirection()
	 public void setDirection(int dir){
//		 if(dir != null)
//			 this.direction = dir;
		 if(isValidDirection(dir))
			 this.direction = dir;
			 
	 }
	 
	 /**
	  * Set the energy of the robot.
	  * @param 	energy
	  * 		The new energy of the robot
	  * @pre 	The energy must be a valid energy.
	  * 		| isValidEnergy(energy)
	  * @post 	The new energy of the robot is equal to the given energy.
	  * 		| new.getEnergy() == energy
	  */
	 public void setEnergy(double energy){
		 assert isValidEnergy(energy);
		 this.energy = energy;
	 }
	 
	 /**
	  * A method that returns the energy as a fraction of the maximum amount.
	  * @return Returns how much of the robot's energy is left as a fraction of the maximum amount.
	  * 		| result == (this.getEnergy()/this.getMaxEnergy())
	  */
	 public double getEnergyPercentage(){
		 return this.getEnergy()/this.getMaxEnergy();
	 }
	 
	 /**
	  * A method that recharges the robot with a given amount of energy.
	  * @param 	energy
	  * 		The amount of energy this method must recharge.
	  * @post 	The new energy equals the sum of the given energy and former energy.
	  * 		| if(isValidEnergy(this.getEnergy() + energy))
	  * 		|	then new.getEnergy() == this.getEnergy() + energy
	  */
	 public void rechargeEnergy(double energy){
		 setEnergy(this.getEnergy() + energy);
	 }
	 /**
	  * A method to use up a given amount of energy.
	  * @param 	energy
	  * 		The energy the robot has to use.
	  * @post 	The new energy of the robot equals the difference between the former energy and the given energy.
	  * 		| if(isValidEnergy(this.getEnergy() - energy))
	  * 		|   then new.getEnergy() == this.getEnergy() - energy
	  */
	 public void useEnergy(double energy){
		 setEnergy(this.getEnergy() - energy);
	 }
	 /**
	  * A method to check if the energy is valid.
	  * 
	  * @param 	energy
	  * 		The energy we need to check.
	  * @return	Returns false if energy is smaller than zero or greater than getMaxEnergy().
	  * 		| result == ((energy >= 0) && (energy <= getMaxEnergy()))
	  */
	 public boolean isValidEnergy(double energy){
		 return ((energy >= 0) && (energy <= getMaxEnergy()));
	 }
	 
	 /**
	  * A method to check if the coordinate is valid.
	  * @param 	coord
	  * 		The coordinate we need to check.
	  * @return	Returns false if coord is smaller than zero or greater than getBoardSize() and true otherwise.
	  * 		| result == ((coord >= 0) && (coord <= getBoardSize()))
	  */
	 public boolean isValidCoord(long coord){
		 return (coord >= 0) && (coord <= getBoardSize());
	 }
		 
	 /**
	  * A method to check if the direction is valid.
	  * @param 	dir
	  * 		The direction we need to check.
	  * @return	Returns true if the given direction is 0, 1, 2 or 3 and false otherwise.
	  * 		| result == (dir>= 0 && dir <=3)
	  */
	 public boolean isValidDirection(long dir){
		 return (dir>= 0 && dir <=3);
	 }
	 
	/**
	 * A method to turn the robot clockwise.
	 * @pre	 	The robot must have enough energy to turn.
	 * 			| isValidEnergy(getEnergy()- getCostTurn())
	 * @post 	The robot will have used up energy to turn.
	 * 			| new.getEnergy() == getEnergy() - getCostTurn()
	 * @post	The robot will face in the direction clockwise of the current direction.
	 * 			|new.getDirection = (getDirection()+1)%4
	 */
	 //XXX
//	 * @post	 If the current direction is LEFT, the robot's direction will change to UP.
//	 * 			| if(getDirection() == Direction.LEFT)
//	 * 			|	then new.getDirection() == Direction.UP
//	 * @post 	If the current direction is UP, the robot's direction will change to RIGHT.
//	 * 			| if(getDirection() == Direction.UP)
//	 * 			|	then new.getDirection() == Direction.RIGHT
//	 * @post 	If the current direction is RIGHT, the robot's direction will change to DOWN.
//	 * 			| if(getDirection() == Direction.RIGHT)
//	 * 			|	then new.getDirection() == Direction.DOWN
//	 * @post 	If the current direction is DOWN, the robot's direction will change to LEFT.
//	 * 			| if(getDirection() == Direction.DOWN)
//	 * 			|	then new.getDirection() == Direction.LEFT
	public void turnClockwise(){
		useEnergy(COST_TURN); //the precondition is checked in the useEnergy() method
		
		//XXX: old code
//		switch(getDirection()){
//		
//			case LEFT: 	setDirection(Direction.UP);
//							
//			case UP: 	setDirection(Direction.RIGHT);
//						
//			case RIGHT: setDirection(Direction.DOWN);
//				
//			case DOWN: 	setDirection(Direction.LEFT);
//				
//			default: //INVALID, can never be executed //TODO: wat moet hier staan?
//		}
		
		setDirection((getDirection()+1)%4);
		
	}
	
	/**
	 * A method to turn the robot counter-clockwise.
	 * @pre		The robot must have enough energy to turn.
	 * 			| isValidEnergy(getEnergy()- getCostTurn())
	 * @post 	The robot will have used up energy to turn.
	 * 			| new.getEnergy() == getEnergy() - getCostTurn()
	 * @post	The robot will face in the direction counter-clockwise of the current direction.
	 * 			|new.getDirection = (getDirection()-1)%4
	 */
	 //XXX
//	 @post 	If the current direction is LEFT, the robot's direction will change to DOWN.
//	 * 			| if(getDirection() == Direction.LEFT)
//	 * 			|	then new.getDirection() == Direction.DOWN
//	 * @post 	If the current direction is UP, the robot's direction will change to LEFT.
//	 * 			| if(getDirection() == Direction.UP)
//	 * 			|	then new.getDirection() == Direction.LEFT
//	 * @post 	If the current direction is RIGHT, the robot's direction will change to UP.
//	 * 			| if(getDirection() == Direction.RIGHT)
//	 * 			|	then new.getDirection() == Direction.UP
//	 * @post 	If the current direction is DOWN, the robot's direction will change to RIGHT.
//	 * 			| if(getDirection() == Direction.DOWN)
//	 * 			|	then new.getDirection() == Direction.RIGHT
	public void turnCounterClockwise(){
		useEnergy(COST_TURN); // the precondition is checked in the useEnergy() method
		
		//XXX: old code
//		switch(getDirection()){
//		
//			case LEFT: 	setDirection(Direction.DOWN);
//							
//			case UP: 	setDirection(Direction.LEFT);
//						
//			case RIGHT: setDirection(Direction.UP);
//				
//			case DOWN: 	setDirection(Direction.RIGHT);
//				
//			default: //INVALID, can never be executed //TODO: wat moet hier staan?
//		}
		
		setDirection((getDirection()-1)%4);
		
	}
	
	/**
	 * A method to move the robot.
	 * @pre		The robot must have enough energy to move.
	 * 			| isValidEnergy(getEnergy() - getCostMove())
	 * @post 	The robot will have used up energy to move.
	 * 			| new.getEnergy() == getEnergy() - getCostMove()
	 * @throws	IndexOutOfBoundsException
	 * 			If the robot would try to move outside of the board.
	 * 			| !isValidCoord(new.getXPos()) || !isValidCoord(new.getYPos()) 	//TODO: juist geformuleerd?
	 * @post	If the robot's direction is 0(up), his Y coordinate will decrease with one and the X coordinate will remain the same.
	 * 			| if(getDirection() == 0)
	 * 			|	then (new.getXPos() == getXPos()) && (new.getYPos() == getYPos()-1)
	 * @post	If the robot's direction is 1(right), his X coordinate will increase with one and the Y coordinate will remain the same.
	 * 			| if(getDirection() == 1)
	 * 			|	then (new.getXPos() == getXPos()+1) && (new.getYPos() == getYPos())
	 * @post	If the robot's direction is 2(down), his Y coordinate will increase with one and the X coordinate will remain the same.
	 * 			| if(getDirection() == 2)
	 * 			|	then (new.getXPos() == getXPos()) && (new.getYPos() == getYPos()+1)
	 * @post	If the robot's direction is 3(left), his X coordinate will decrease with one and the Y coordinate will remain the same.
	 * 			| if(getDirection() == 3)
	 * 			|	then (new.getXPos() == getXPos()-1) && (new.getYPos() == getYPos()) 
	 */
	 //XXX
	
	public void move() throws IndexOutOfBoundsException{
		useEnergy(COST_MOVE); // the precondition is checked in the useEnergy() method
		if(getDirection() == 0){
			try{// move up
				setYPos(getYPos()-1);
			}
			catch(IllegalArgumentException e){
				throw new IndexOutOfBoundsException();
			}
		}
		if(getDirection() == 1){
			try{//move right
				setXPos(getXPos()+1);
			}
			catch(IllegalArgumentException e){
				throw new IndexOutOfBoundsException();
			}
		}
		if(getDirection() == 2){
			try{//move down
				setYPos(getYPos()+1);
			}
			catch(IllegalArgumentException e){
				throw new IndexOutOfBoundsException();
			}
		}
		if(getDirection() == 3){
			try{//move left
				setXPos(getXPos()-1);
			}
			catch(IllegalArgumentException e){
				throw new IndexOutOfBoundsException();
			}
		}
	}	
}
